﻿using System;
using System.Collections;
using System.Data;
using System.Data.OleDb;
using System.IO;
using System.Management;
using System.Text;
using System.Windows.Forms;

namespace ServiceDependencyViewer
{
    public partial class Viewer : Form
    {
        #region Private Members

        private ManagementObjectCollection Services;
        private BindingSource ServiceBinder = new BindingSource();
        private DataTable ServiceTable = new DataTable();

        private ArrayList Processes = new ArrayList();
        private BindingSource ProcessBinder = new BindingSource();
        private DataTable ProcessTable = new DataTable();

        private ManagementObjectCollection Drivers;
        private BindingSource DriverBinder = new BindingSource();
        private DataTable DriverTable = new DataTable();

        #endregion

        #region Constructors

        public Viewer()
        {
            InitializeComponent();
            GetServiceData();
        }

        #endregion

        #region Private Methods

        private void GetData()
        {
            Details.Clear();

            GetServiceData();
            GetProcessData();
            GetDriverData();
        }

        private void GetServiceData()
        {
            ServiceTree.Sorted = true;

            PopulateTree(ServiceTree, ref Services,
                "Select * From Win32_Service");

            ServiceTable.Rows.Clear();
            ServiceBinder.ResetBindings(false);
            if (ServiceTable.Columns.Count == 0)
            {
                AddTableColumns(
                    "Win32_Service", ServiceTable);
            }
            PopulateTable(ServiceTable, Services);

            ServiceBinder.DataSource = ServiceTable;
            ServiceGrid.DataSource = ServiceBinder;
        }

        private void GetProcessData()
        {
            ProcessTree.Sorted = true;

            Processes.Clear();
            PopulateProcessTree();

            ProcessTable.Rows.Clear();
            ProcessBinder.ResetBindings(false);
            if (ProcessTable.Columns.Count == 0)
            {
                AddTableColumns(
                    "Win32_Process", ProcessTable);
            }
            PopulateTable(ProcessTable, Processes);

            ProcessBinder.DataSource = ProcessTable;
            ProcessGrid.DataSource = ProcessBinder;
        }

        private void GetDriverData()
        {
            DriverTree.Sorted = true;

            PopulateTree(DriverTree, ref Drivers,
                "Select * From Win32_SystemDriver");

            DriverTable.Rows.Clear();
            DriverBinder.ResetBindings(false);
            if (DriverTable.Columns.Count == 0)
            {
                AddTableColumns(
                    "Win32_SystemDriver", DriverTable);
            }
            PopulateTable(DriverTable, Drivers);

            DriverBinder.DataSource = DriverTable;
            DriverGrid.DataSource = DriverBinder;
        }

        private void AddTableColumns(
            string className, DataTable Table)
        {
            ManagementClass cls =
                new ManagementClass(className);

            foreach (PropertyData p in cls.Properties)
            {
                Table.Columns.Add(p.Name);
            }
        }

        private void PopulateProcessTree()
        {
            ProcessTree.Nodes.Clear();

            foreach (ManagementObject service in Services)
            {
                service.Get();

                if (service["ProcessId"].ToString() != "0")
                {
                    ManagementObject process =
                        new ManagementObject(@"Win32_Process.Handle='" +
                        service["ProcessId"].ToString() + @"'");

                    if (!ProcessTree.Nodes.ContainsKey(service["ProcessId"].ToString()))
                    {
                        Processes.Add(process);

                        TreeNode node = new TreeNode();
                        node.Name = process["Handle"].ToString();
                        node.Text = process["Name"].ToString();
                        node.Tag = process;
                        node.ImageIndex = 1;

                        TreeNode serviceNode = new TreeNode();
                        serviceNode.Name = service["Name"].ToString();
                        serviceNode.Text = service["DisplayName"].ToString();
                        serviceNode.ImageIndex = 0;

                        node.Nodes.Add(serviceNode);
                        ProcessTree.Nodes.Add(node);
                    }
                    else
                    {
                        TreeNode serviceNode = new TreeNode();
                        serviceNode.Name = service["Name"].ToString();
                        serviceNode.Text = service["DisplayName"].ToString();
                        serviceNode.ImageIndex = 0;

                        ProcessTree.Nodes[
                            service["ProcessId"].ToString()]
                            .Nodes.Add(serviceNode);
                    }
                }
            }
        }

        private void PopulateTree(TreeView tree,
            ref ManagementObjectCollection instances, string query)
        {
            tree.Nodes.Clear();

            ManagementObjectSearcher searcher =
                new ManagementObjectSearcher();
            searcher.Query.QueryString = query;

            instances = searcher.Get();

            foreach (ManagementObject instance in instances)
            {
                if (!tree.Nodes.ContainsKey(instance["Name"].ToString()))
                {
                    TreeNode node = new TreeNode();
                    node.Name = instance["Name"].ToString();
                    node.Text = instance["DisplayName"].ToString();
                    node.Tag = instance;

                    TreeNode antecedent = new TreeNode();
                    antecedent.Name = "Antecedent";
                    antecedent.Text = "Antecedent";
                    antecedent.ImageIndex = 4;
                    antecedent.Nodes.Add("Dummy", "Dummy");

                    TreeNode dependent = new TreeNode();
                    dependent.Name = "Dependent";
                    dependent.Text = "Dependent";
                    dependent.ImageIndex = 3;
                    dependent.Nodes.Add("Dummy", "Dummy");

                    node.Nodes.Add(antecedent);
                    node.Nodes.Add(dependent);

                    tree.Nodes.Add(node);
                }
            }
        }

        private void PopulateTable(
            DataTable table, ICollection instances)
        {
            foreach (ManagementObject instance in instances)
            {
                DataRow row = table.NewRow();

                foreach (DataColumn c in table.Columns)
                {
                    if (instance[c.ColumnName] != null)
                    {
                        row[c.ColumnName] =
                            instance[c.ColumnName].ToString();
                    }
                    else
                    {
                        row[c.ColumnName] = "<Null>";
                    }
                }

                table.Rows.Add(row);
            }
        }

        private void AddDependent(
            ManagementObject service, TreeNode node)
        {
            ManagementObjectCollection dependents =
                service.GetRelated(
                    string.Empty,"Win32_DependentService",
                    string.Empty, string.Empty, "Dependent",
                    string.Empty, false, null);

            foreach (ManagementObject dependent in dependents)
            {
                TreeNode dependentNode = new TreeNode();
                dependentNode.Name = dependent["Name"].ToString();
                dependentNode.Text = dependent["DisplayName"].ToString();
                dependentNode.Tag = "Dependent";
                dependentNode.Nodes.Add("Dummy", "Dummy");

                if (dependent.ClassPath.ClassName == "Win32_Service")
                {
                    dependentNode.ImageIndex = 0;
                }
                else if (dependent.ClassPath.ClassName == "Win32_SystemDriver")
                {
                    dependentNode.ImageIndex = 2;
                }

                node.Nodes.Add(dependentNode);
            }
        }

        private void AddAntecedent(
            ManagementObject service, TreeNode node)
        {
            ManagementObjectCollection antecedents =
                service.GetRelated(
                    string.Empty, "Win32_DependentService",
                    string.Empty, string.Empty, "Antecedent",
                    string.Empty, false, null);

            foreach (ManagementObject antecedent in antecedents)
            {
                TreeNode antecedentNode = new TreeNode();
                antecedentNode.Name = antecedent["Name"].ToString();
                antecedentNode.Text = antecedent["DisplayName"].ToString();
                antecedentNode.Tag = "Antecedent";
                antecedentNode.Nodes.Add("Dummy", "Dummy");

                node.Nodes.Add(antecedentNode);

                if (antecedent.ClassPath.ClassName == "Win32_Service")
                {
                    antecedentNode.ImageIndex = 0;
                }
                else if (antecedent.ClassPath.ClassName == "Win32_SystemDriver")
                {
                    antecedentNode.ImageIndex = 2;
                }
            }
        }

        private void ShowDetails(TreeNode node)
        {
            Details.Clear();

            if (node.Name != "Antecedent" &
                node.Name != "Dependent")
            {
                ManagementObject instance;

                if (ServiceTree.Nodes.ContainsKey(node.Name))
                {
                    instance = ServiceTree.Nodes[node.Name].Tag
                        as ManagementObject;
                }
                else if (DriverTree.Nodes.ContainsKey(node.Name))
                {
                    instance = DriverTree.Nodes[node.Name].Tag
                       as ManagementObject;
                }
                else
                {
                    instance = ProcessTree.Nodes[node.Name].Tag
                        as ManagementObject;
                }

                Details.Text =
                    GetWMIObjectProperties(instance.Properties);
            }
        }

        private string GetWMIObjectProperties(
            PropertyDataCollection properties)
        {
            StringBuilder text = new StringBuilder();

            foreach (PropertyData property in properties)
            {
                text.Append("  ");
                text.Append(property.Name);
                text.Append(new string(' ', 30 - property.Name.Length));
                text.Append(" : ");
                if (property.Value != null)
                {
                    text.Append(property.Value.ToString());
                }
                else
                {
                    text.Append("<Null>");
                }
                text.Append(Environment.NewLine);
            }

            return text.ToString();
        }

        #endregion

        #region Service and Driver TreeView Event Handlers

        private void Tree_BeforeExpand(
            object sender, TreeViewCancelEventArgs e)
        {
            if (DriverTree.Nodes.Count == 0)
            {
                GetDriverData();
            }

            if(e.Node.Nodes.ContainsKey("Dummy"))
            {
                e.Node.Nodes["Dummy"].Remove();
            }

            switch(e.Node.Name)
            {
                case "Dependent": 
                    if(e.Node.Nodes.Count == 0)
                    {
                        AddDependent(e.Node.Parent.Tag 
                            as ManagementObject, e.Node);
                    }
                        break;

                case "Antecedent":
                    if(e.Node.Nodes.Count == 0)
                    {
                        AddAntecedent(e.Node.Parent.Tag 
                            as ManagementObject, e.Node);
                    }
                    break;

                default:
                    if (e.Node.Level > 1 & e.Node.Nodes.Count == 0)
                    {
                        ManagementObject instance =  null;

                        if (ServiceTree.Nodes.ContainsKey(e.Node.Name))
                        {
                            instance =
                                ServiceTree.Nodes[e.Node.Name].Tag
                                as ManagementObject;
                        }
                        else
                        {
                            instance =
                            DriverTree.Nodes[e.Node.Name].Tag
                            as ManagementObject;
                        }

                        if (e.Node.Tag as string == "Antecedent")
                        {
                            AddAntecedent(instance, e.Node);
                        }
                        else if (e.Node.Tag as string == "Dependent")
                        {
                            AddDependent(instance, e.Node);
                        }
                    }
                    break;
            }
        }

        private void ServiceTree_NodeMouseClick(
            object sender, TreeNodeMouseClickEventArgs e)
        {
            //ServiceTree.SelectedNode = e.Node;
            //ShowServiceDetails(e.Node);
        }

        private void ServiceTree_AfterSelect(
            object sender, TreeViewEventArgs e)
        {
            ShowDetails(e.Node);
        }

        private void DriverTree_AfterSelect(
            object sender, TreeViewEventArgs e)
        {
            ShowDetails(e.Node);
        }

        private void Tree_BeforeSelect(
            object sender, TreeViewCancelEventArgs e)
        {
            e.Node.SelectedImageIndex = e.Node.ImageIndex;
        }

        #endregion

        #region Process TreeView Event Handlers

        private void ProcessTree_AfterSelect(
            object sender, TreeViewEventArgs e)
        {
            ShowDetails(e.Node);
        }

        #endregion

        #region Menu Event Handlers

        private void Menu_Exit_Click(
            object sender, System.EventArgs e)
        {
            Close();
        }

        private void Menu_Details_Click(
            object sender, EventArgs e)
        {
            Menu_Details.Checked = true;
            Menu_ServicePropertyGrid.Checked = false;
            Menu_ProcessPropertyGrid.Checked = false;
            Menu_DriverPropertyGrid.Checked = false;
        }

        private void Menu_ServicePropertyGrid_Click(
            object sender, EventArgs e)
        {
            Menu_Details.Checked = false;
            Menu_ServicePropertyGrid.Checked = true;
            Menu_ProcessPropertyGrid.Checked = false;
            Menu_DriverPropertyGrid.Checked = false;
        }

        private void Menu_ProcessPropertyGrid_Click(
            object sender, EventArgs e)
        {
            Menu_Details.Checked = false;
            Menu_ServicePropertyGrid.Checked = false;
            Menu_ProcessPropertyGrid.Checked = true;
            Menu_DriverPropertyGrid.Checked = false;
        }

        private void Menu_DriverPropertyGrid_Click(
            object sender, EventArgs e)
        {
            Menu_Details.Checked = false;
            Menu_ServicePropertyGrid.Checked = false;
            Menu_ProcessPropertyGrid.Checked = false;
            Menu_DriverPropertyGrid.Checked = true;
        }

        private void Menu_ServiceDetails_CheckedChanged(
            object sender, EventArgs e)
        {
            if (Menu_Details.Checked)
            {
                Details.Visible = true;
                ServiceGrid.Visible = false;
                ProcessGrid.Visible = false;
                DriverGrid.Visible = false;
            }
        }

        private void Menu_ServicePropertyGrid_CheckedChanged(
            object sender, EventArgs e)
        {
            if (Menu_ServicePropertyGrid.Checked)
            {
                ServiceGrid.Visible = true;
                Details.Visible = false;
                ProcessGrid.Visible = false;
                DriverGrid.Visible = false;
            }
        }

        private void Menu_ProcessPropertyGrid_CheckedChanged(
            object sender, EventArgs e)
        {
            if (ProcessTree.Nodes.Count == 0)
            {
                GetProcessData();
            }

            if (Menu_ProcessPropertyGrid.Checked)
            {
                ServiceGrid.Visible = false;
                Details.Visible = false;
                ProcessGrid.Visible = true;
                DriverGrid.Visible = false;
            }
        }

        private void Menu_DriverPropertyGrid_CheckedChanged(
            object sender, EventArgs e)
        {
            if (DriverTree.Nodes.Count == 0)
            {
                GetDriverData();
            }

            if (Menu_DriverPropertyGrid.Checked)
            {
                ServiceGrid.Visible = false;
                Details.Visible = false;
                ProcessGrid.Visible = false;
                DriverGrid.Visible = true;
            }
        }

        private void Menu_Refresh_Click(
            object sender, EventArgs e)
        {
            GetData();
        }

        private void Menu_About_Click(
            object sender, EventArgs e)
        {
            About about = new About();
            about.ShowDialog();
        }

        #endregion

        #region Details Context Menu

        private void copySelectedTextToolStripMenuItem_Click(
            object sender, EventArgs e)
        {
            if (Details.SelectedText.Length > 0)
            {
                Clipboard.SetText(
                    Details.SelectedText.Replace(
                    "\n", Environment.NewLine));
            }
        }

        private void copyAllToolStripMenuItem_Click(
            object sender, EventArgs e)
        {
            if (Details.Text.Length > 0)
            {
                Clipboard.SetText(
                    Details.Text.Replace(
                    "\n", Environment.NewLine));
            }
        }

        private void saveAsToolStripMenuItem_Click(
            object sender, EventArgs e)
        {
            SaveDialog.DefaultExt = "txt";

            if (SaveDialog.ShowDialog() == DialogResult.OK)
            {
                StreamWriter writer =
                    new StreamWriter(SaveDialog.FileName);
                writer.Write(Details.Text.Replace(
                    "\n", Environment.NewLine));
                writer.Close();
            }
        }

        private void DetailsMenu_Opening(
            object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (Details.WordWrap)
            {
                wrapTextToolStripMenuItem.Checked = true;
            }
            else
            {
                wrapTextToolStripMenuItem.Checked = false;
            }
        }

        private void wrapTextToolStripMenuItem_Click(
            object sender, EventArgs e)
        {
            if (Details.WordWrap)
            {
                Details.WordWrap = false;
            }
            else
            {
                Details.WordWrap = true;
            }
        }

        #endregion

        #region Grid Context Menu

        private void csvToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveDialog.DefaultExt = "csv";
            SaveAsTxt(",");
        }

        private void tsvToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveDialog.DefaultExt = "tsv";
            SaveAsTxt("\t");
        }

        private void xlsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveDialog.DefaultExt = "xls";

            if (SaveDialog.ShowDialog() == DialogResult.OK)
            {
                if (File.Exists(SaveDialog.FileName))
                {
                    File.Delete(SaveDialog.FileName);
                }

                if (ServiceGrid.Visible)
                {
                    SaveAsXls(ServiceGrid, SaveDialog.FileName);
                }
                else if (ProcessGrid.Visible)
                {
                    SaveAsXls(ProcessGrid, SaveDialog.FileName);
                }
                else
                {
                    SaveAsXls(DriverGrid, SaveDialog.FileName);
                }
            }
        }

        #endregion

        #region Export Data Functions

        private string EscapeField(string field, string separator)
        {
            string retVal = "";

            if (field.Contains(separator))
            {
                if(field.Contains("\""))
                {
                    retVal =  field.Replace("\"", "\"\"");
                }
            }
            else
            {
                retVal = field;
            }

            return "\"" + retVal + "\"";
        }

        private string GetDelimitedLine(
            DataGridViewRow row, string separator)
        {
            StringBuilder csvRow = new StringBuilder();

            foreach (DataGridViewColumn c in row.DataGridView.Columns)
            {
                csvRow.Append(EscapeField(
                    row.Cells[c.Name].Value.ToString(), separator));
                csvRow.Append(separator);
            }

            csvRow.Remove(csvRow.Length - 1, 1);
            csvRow.Append(Environment.NewLine);
            return csvRow.ToString();
        }

        private string GetDelimitedText(
            DataGridView grid, string separator)
        {
            StringBuilder csvTable = new StringBuilder();

            if (grid.SelectedRows.Count > 0)
            {
                foreach (DataGridViewRow row in grid.SelectedRows)
                {
                    if (!row.IsNewRow)
                    {
                        csvTable.Append(GetDelimitedLine(row, separator));
                    }
                }
            }
            else
            {
                foreach (DataGridViewRow row in grid.Rows)
                {
                    csvTable.Append(GetDelimitedLine(row, separator));
                }
            }

            return csvTable.ToString();
        }

        private void SaveAsTxt(string separator)
        {
            if (SaveDialog.ShowDialog() == DialogResult.OK)
            {
                StreamWriter writer =
                    new StreamWriter(SaveDialog.FileName);
                if (ServiceGrid.Visible)
                {
                    writer.Write(GetDelimitedText(ServiceGrid, separator));
                }
                else
                {
                    writer.Write(GetDelimitedText(ProcessGrid, separator));
                }
                writer.Close();
            }
        }

        private void SaveAsXls(DataGridView grid, string fileName)
        {
            OleDbConnection conn = new OleDbConnection(
                @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
                fileName + @";Extended Properties=""Excel 8.0;HDR=Yes;"";");

            conn.Open();

            OleDbCommand cmd = new OleDbCommand(
                    "CREATE TABLE [Sheet1] (" +
                     GetExcelTableColumnNames(grid.Columns) + ")");

            cmd.Connection = conn;
            cmd.ExecuteNonQuery();

            if (grid.SelectedRows.Count == 0)
            {
                foreach (DataGridViewRow gridRow in grid.Rows)
                {
                    if (!gridRow.IsNewRow)
                    {
                        cmd.CommandText = GetInsertCommandString(gridRow);
                        cmd.ExecuteNonQuery();
                    }
                }
            }
            else
            {
                foreach (DataGridViewRow gridRow in grid.SelectedRows)
                {
                    if (!gridRow.IsNewRow)
                    {
                        cmd.CommandText = GetInsertCommandString(gridRow);
                        cmd.ExecuteNonQuery();
                    }
                }
            }

            conn.Close();
        }

        private string GetInsertCommandString(DataGridViewRow row)
        {
            StringBuilder names = new StringBuilder();
            StringBuilder values = new StringBuilder();

            names.Append("(");
            values.Append("(");

            foreach (DataGridViewCell cell in row.Cells)
            {
                names.Append(cell.OwningColumn.Name);
                names.Append(",");

                values.Append("'");
                values.Append(cell.Value);
                values.Append("',");
            }

            names.Remove(names.Length - 1, 1);
            values.Remove(values.Length - 1, 1);

            names.Append(")");
            values.Append(")");

            return "Insert Into [Sheet1] " + names.ToString() +
                " values " + values.ToString();

        }

        private string GetExcelTableColumnNames(
            DataGridViewColumnCollection columns)
        {
            StringBuilder retVal = new StringBuilder();

            foreach (DataGridViewColumn column in columns)
            {
                retVal.Append(column.Name);
                retVal.Append(" text,");
            }
            retVal.Remove(retVal.Length - 1, 1);
            return retVal.ToString();
        }

        #endregion

        #region Tab Control functions

        private void ProcessTab_Enter(object sender, EventArgs e)
        {
            if (ProcessTree.Nodes.Count == 0)
            {
                GetProcessData();
            }
        }

        private void DriverTab_Enter(object sender, EventArgs e)
        {
            if (DriverTree.Nodes.Count == 0)
            {
                GetDriverData();
            }
        }

        #endregion

    }
}
